/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.delegate;

import com.alibaba.fastjson2.JSONObject;
import com.google.common.base.Strings;
import com.je.bpm.common.el.ExpressionResolver;
import com.je.bpm.common.el.JuelExpressionResolver;
import com.je.bpm.core.model.*;
import com.je.bpm.core.model.process.Process;
import com.je.bpm.core.model.task.KaiteTaskCategoryEnum;
import com.je.bpm.core.model.task.TaskWithFieldExtensions;
import com.je.bpm.engine.ActivitiException;
import com.je.bpm.engine.impl.bpmn.behavior.KaiteBaseUserTaskActivityBehavior;
import com.je.bpm.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import com.je.bpm.engine.impl.cmd.SubmitTypeEnum;
import com.je.bpm.engine.impl.context.Context;
import com.je.bpm.engine.impl.el.ExpressionManager;
import com.je.bpm.engine.impl.el.FixedValue;
import com.je.bpm.engine.impl.identity.Authentication;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntity;
import com.je.bpm.engine.impl.persistence.entity.TaskEntity;
import com.je.bpm.engine.impl.util.ProcessDefinitionUtil;
import com.je.bpm.engine.task.Task;
import com.je.bpm.engine.upcoming.UpcomingCommentInfoDTO;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Class that provides helper operations for use in the {@link JavaDelegate},
 * {@link ActivityBehavior}, {@link ExecutionListener} and {@link TaskListener}
 * interfaces.
 */
public class DelegateHelper {

    /**
     * To be used in an {@link ActivityBehavior} or {@link JavaDelegate}: leaves
     * according to the default BPMN 2.0 rules: all sequenceflow with a condition
     * that evaluates to true are followed.
     */
    public static void leaveDelegate(DelegateExecution delegateExecution) {
        Context.getAgenda().planTakeOutgoingSequenceFlowsOperation((ExecutionEntity) delegateExecution, true);
    }

    /**
     * To be used in an {@link ActivityBehavior} or {@link JavaDelegate}: leaves
     * the current activity via one specific sequenceflow.
     */
    public static void leaveDelegate(DelegateExecution delegateExecution,
                                     String sequenceFlowId) {
        String processDefinitionId = delegateExecution.getProcessDefinitionId();
        Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
        FlowElement flowElement = process.getFlowElement(sequenceFlowId);
        if (flowElement instanceof SequenceFlow) {
            delegateExecution.setCurrentFlowElement(flowElement);
            Context.getAgenda().planTakeOutgoingSequenceFlowsOperation((ExecutionEntity) delegateExecution, false);
        } else {
            throw new ActivitiException(sequenceFlowId + " does not match a sequence flow");
        }
    }

    /**
     * Returns the {@link BpmnModel} matching the process definition bpmn model
     * for the process definition of the passed {@link DelegateExecution}.
     */
    public static BpmnModel getBpmnModel(DelegateExecution execution) {
        if (execution == null) {
            throw new ActivitiException("Null execution passed");
        }
        return ProcessDefinitionUtil.getBpmnModel(execution.getProcessDefinitionId());
    }

    /**
     * Returns the current {@link FlowElement} where the {@link DelegateExecution} is currently at.
     */
    public static FlowElement getFlowElement(DelegateExecution execution) {
        BpmnModel bpmnModel = getBpmnModel(execution);
        FlowElement flowElement = bpmnModel.getFlowElement(execution.getCurrentActivityId());
        if (flowElement == null) {
            throw new ActivitiException("Could not find a FlowElement for activityId " + execution.getCurrentActivityId());
        }
        return flowElement;
    }

    /**
     * Returns whether or not the provided execution is being use for executing an {@link ExecutionListener}.
     */
    public static boolean isExecutingExecutionListener(DelegateExecution execution) {
        return execution.getCurrentActivitiListener() != null;
    }

    /**
     * Returns for the activityId of the passed {@link DelegateExecution} the
     * {@link Map} of {@link ExtensionElement} instances. These represent the
     * extension elements defined in the BPMN 2.0 XML as part of that particular
     * activity.
     * <p>
     * If the execution is currently being used for executing an
     * {@link ExecutionListener}, the extension elements of the listener will be
     * used. Use the {@link #getFlowElementExtensionElements(DelegateExecution)}
     * or {@link #getListenerExtensionElements(DelegateExecution)} instead to
     * specifically get the extension elements of either the flow element or the
     * listener.
     */
    public static Map<String, List<ExtensionElement>> getExtensionElements(DelegateExecution execution) {
        if (isExecutingExecutionListener(execution)) {
            return getListenerExtensionElements(execution);
        } else {
            return getFlowElementExtensionElements(execution);
        }
    }

    public static Map<String, List<ExtensionElement>> getFlowElementExtensionElements(DelegateExecution execution) {
        return getFlowElement(execution).getExtensionElements();
    }

    public static Map<String, List<ExtensionElement>> getListenerExtensionElements(DelegateExecution execution) {
        return execution.getCurrentActivitiListener().getExtensionElements();
    }

    /**
     * Returns the list of field extensions, represented as instances of
     * {@link FieldExtension}, for the current activity of the passed
     * {@link DelegateExecution}.
     * <p>
     * If the execution is currently being used for executing an
     * {@link ExecutionListener}, the fields of the listener will be returned. Use
     * {@link #getFlowElementFields(DelegateExecution)} or
     * {@link #getListenerFields(DelegateExecution)} if needing the flow element
     * of listener fields specifically.
     */
    public static List<FieldExtension> getFields(DelegateExecution execution) {
        if (isExecutingExecutionListener(execution)) {
            return getListenerFields(execution);
        } else {
            return getFlowElementFields(execution);
        }
    }

    public static List<FieldExtension> getFlowElementFields(DelegateExecution execution) {
        FlowElement flowElement = getFlowElement(execution);
        if (flowElement instanceof TaskWithFieldExtensions) {
            return ((TaskWithFieldExtensions) flowElement).getFieldExtensions();
        }
        return new ArrayList<FieldExtension>();
    }

    public static List<FieldExtension> getListenerFields(DelegateExecution execution) {
        return execution.getCurrentActivitiListener().getFieldExtensions();
    }

    /**
     * Returns the {@link FieldExtension} matching the provided 'fieldName' which
     * is defined for the current activity of the provided
     * {@link DelegateExecution}.
     * <p>
     * Returns null if no such {@link FieldExtension} can be found.
     * <p>
     * If the execution is currently being used for executing an
     * {@link ExecutionListener}, the field of the listener will be returned. Use
     * {@link #getFlowElementField(DelegateExecution, String)} or
     * {@link #getListenerField(DelegateExecution, String)} for specifically
     * getting the field from either the flow element or the listener.
     */
    public static FieldExtension getField(DelegateExecution execution,
                                          String fieldName) {
        if (isExecutingExecutionListener(execution)) {
            return getListenerField(execution,
                    fieldName);
        } else {
            return getFlowElementField(execution,
                    fieldName);
        }
    }

    public static FieldExtension getFlowElementField(DelegateExecution execution,
                                                     String fieldName) {
        List<FieldExtension> fieldExtensions = getFlowElementFields(execution);
        if (fieldExtensions == null || fieldExtensions.size() == 0) {
            return null;
        }
        for (FieldExtension fieldExtension : fieldExtensions) {
            if (fieldExtension.getFieldName() != null && fieldExtension.getFieldName().equals(fieldName)) {
                return fieldExtension;
            }
        }
        return null;
    }

    public static FieldExtension getListenerField(DelegateExecution execution,
                                                  String fieldName) {
        List<FieldExtension> fieldExtensions = getListenerFields(execution);
        if (fieldExtensions == null || fieldExtensions.size() == 0) {
            return null;
        }
        for (FieldExtension fieldExtension : fieldExtensions) {
            if (fieldExtension.getFieldName() != null && fieldExtension.getFieldName().equals(fieldName)) {
                return fieldExtension;
            }
        }
        return null;
    }

    /**
     * Creates an {@link Expression} for the {@link FieldExtension}.
     */
    public static Expression createExpressionForField(FieldExtension fieldExtension) {
        if (StringUtils.isNotEmpty(fieldExtension.getExpression())) {
            ExpressionManager expressionManager = Context.getProcessEngineConfiguration().getExpressionManager();
            return expressionManager.createExpression(fieldExtension.getExpression());
        } else {
            return new FixedValue(fieldExtension.getStringValue());
        }
    }

    /**
     * Returns the {@link Expression} for the field defined for the current
     * activity of the provided {@link DelegateExecution}.
     * <p>
     * Returns null if no such field was found in the process definition xml.
     * <p>
     * If the execution is currently being used for executing an
     * {@link ExecutionListener}, it will return the field expression for the
     * listener. Use
     * {@link #getFlowElementFieldExpression(DelegateExecution, String)} or
     * {@link #getListenerFieldExpression(DelegateExecution, String)} for
     * specifically getting the flow element or listener field expression.
     */
    public static Expression getFieldExpression(DelegateExecution execution,
                                                String fieldName) {
        if (isExecutingExecutionListener(execution)) {
            return getListenerFieldExpression(execution,
                    fieldName);
        } else {
            return getFlowElementFieldExpression(execution,
                    fieldName);
        }
    }

    /**
     * Similar to {@link #getFieldExpression(DelegateExecution, String)}, but for use within a {@link TaskListener}.
     */
    public static Expression getFieldExpression(DelegateTask task,
                                                String fieldName) {
        if (task.getCurrentActivitiListener() != null) {
            List<FieldExtension> fieldExtensions = task.getCurrentActivitiListener().getFieldExtensions();
            if (fieldExtensions != null && fieldExtensions.size() > 0) {
                for (FieldExtension fieldExtension : fieldExtensions) {
                    if (fieldName.equals(fieldExtension.getFieldName())) {
                        return createExpressionForField(fieldExtension);
                    }
                }
            }
        }
        return null;
    }

    public static Expression getFlowElementFieldExpression(DelegateExecution execution,
                                                           String fieldName) {
        FieldExtension fieldExtension = getFlowElementField(execution,
                fieldName);
        if (fieldExtension != null) {
            return createExpressionForField(fieldExtension);
        }
        return null;
    }

    public static Expression getListenerFieldExpression(DelegateExecution execution,
                                                        String fieldName) {
        FieldExtension fieldExtension = getListenerField(execution,
                fieldName);
        if (fieldExtension != null) {
            return createExpressionForField(fieldExtension);
        }
        return null;
    }

    public static Boolean isBoolean(String conditionExpression, Map<String, Object> bean) {
        if (Strings.isNullOrEmpty(conditionExpression)) {
            return true;
        }
        ExpressionResolver expressionResolver = new JuelExpressionResolver();
        boolean value = expressionResolver.resolveExpression(conditionExpression, bean, Boolean.class);
        return value;
    }


    public static Boolean isCountersignApproval(Task task) {
        if (task instanceof TaskEntity) {
            String logUserId = Authentication.getAuthenticatedUser().getDeptId();
            //会签已审批
            TaskEntity taskEntity = (TaskEntity) task;
            if (taskEntity.getCategory() != null && taskEntity.getCategory().equals(KaiteTaskCategoryEnum.KAITE_COUNTERSIGN_USERTASK.getType())) {
                JSONObject processingInfo = getJsonObject(getMultiInstanceRootExecution(taskEntity.getExecution()), MultiInstanceActivityBehavior.PROCESSING_INFO);
                if (processingInfo != null) {
                    HashMap<String, String> opinions = (HashMap<String, String>) processingInfo.get("opinions");
                    Object opinion = opinions.get(logUserId);
                    if (opinion != null) {
                        return true;
                    }
                }
            }
        }
        return false;
    }


    public static Boolean isHandler(Task task, String assignee, String owner, List<String> candidateIds, Boolean getModel, Boolean isOwner, Boolean isMulti) {
        String logUserId = Authentication.getAuthenticatedUser().getDeptId();
        if (task instanceof TaskEntity) {
            TaskEntity taskEntity = (TaskEntity) task;
            Boolean isPrevAssignee = false;
            if (!isMulti) {
                Object prevAssigneeObject = taskEntity.getVariable(KaiteBaseUserTaskActivityBehavior.PREV_ASSIGNEE);
                if (prevAssigneeObject != null) {
                    String prevAssignee = (String) prevAssigneeObject;
                    if (prevAssignee.equals(logUserId)) {
                        isPrevAssignee = true;
                    }
                }
                if (isPrevAssignee && getModel) {
                    return true;
                }
            }
        }
        //当前登录人在会签节点上
        if (getModel && isCountersignApproval(task)) {
            return true;
        }
        //节点处理人等于当前登录人
        if (assignee != null && assignee.equals(logUserId)) {
            return true;
        }
        //候选人选包含当前登录人
        if (candidateIds.contains(logUserId)) {
            return true;
        }
        //节点所有者
        if ((owner != null && owner.equals(logUserId)) && isOwner) {
            return true;
        }
        return false;
    }

    public static Boolean isHandler(Task task, String assignee, String owner, List<String> candidateIds, Boolean getModel, Boolean isMulti) {
        return isHandler(task, assignee, owner, candidateIds, getModel, true, isMulti);
    }

    protected static DelegateExecution getMultiInstanceRootExecution(DelegateExecution executionEntity) {
        DelegateExecution multiInstanceRootExecution = null;
        DelegateExecution currentExecution = executionEntity;
        while (currentExecution != null && multiInstanceRootExecution == null && currentExecution.getParent() != null) {
            if (currentExecution.isMultiInstanceRoot()) {
                multiInstanceRootExecution = currentExecution;
            } else {
                currentExecution = currentExecution.getParent();
            }
        }
        return multiInstanceRootExecution;
    }

    protected static JSONObject getJsonObject(DelegateExecution execution, String variableName) {
        Object value = execution.getVariable(variableName);
        return (JSONObject) (value != null ? value : null);
    }


    public static UpcomingCommentInfoDTO buildUpcomingInfo(Map<String, Object> bean, String comment,
                                                           SubmitTypeEnum submitType, String beanId, String taskId,
                                                           Map<String, String> params) {
        UpcomingCommentInfoDTO upcomingCommentInfoDTO = UpcomingCommentInfoDTO.build(submitType, bean, beanId, comment,
                taskId, params, null);
        return upcomingCommentInfoDTO;
    }

    public static UpcomingCommentInfoDTO buildUpcomingInfo(Map<String, Object> bean, String comment,
                                                           SubmitTypeEnum submitType, String beanId, String taskId,
                                                           Map<String, String> params, String assigneeJson) {
        UpcomingCommentInfoDTO upcomingCommentInfoDTO = UpcomingCommentInfoDTO.build(submitType, bean, beanId, comment,
                taskId, params, assigneeJson);
        return upcomingCommentInfoDTO;
    }

}
